﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace pingpong
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        int width;
        int height;

        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Texture2D backgroundTexture, menuTexture, gameTexture;

        /// <summary>
        /// Content에서 그려줄 사각형 영역
        /// </summary>
        static readonly Rectangle
            XnaPongLogoRect = new Rectangle(0, 0, 512, 110),
            MenuSingleplayerRect = new Rectangle(0, 110, 512, 38),
            MenuMultiplayerRect = new Rectangle(0, 148, 512, 38),
            MenuExitRect = new Rectangle(0, 185, 512, 38),
            GameLivesRect = new Rectangle(0, 222, 100, 34),
            GameRedWonRect = new Rectangle(151, 222, 155, 34),
            GameBlueWonRect = new Rectangle(338, 222, 165, 34),
            GameRedPaddleRect = new Rectangle(23, 0, 22, 92),
            GameBluePaddleRect = new Rectangle(0, 0, 22, 92),
            GameBallRect = new Rectangle(1, 94, 33, 33),
            GameSmallBallRect = new Rectangle(37, 108, 19, 19);

        float leftPaddlePosition = 0.5f, rightPaddlePosition = 0.5f;
        Vector2 ballPosition = new Vector2(0.5f, 0.5f);
        Vector2 ballSpeedVector = new Vector2(0, 0);

        int leftPlayerLives = 2, rightPlayerLives = 2;

        const float BallSpeedMultiplicator = 0.5f;
        const float ComputerPaddleSpeed = 0.5f;

        enum GameMode
        {
            Menu, Game, GameOver,
        }

        GamePadState gamepad, gamepad2;
        KeyboardState keyboard;

        bool remUpPressed = false,
            remDownPressed = false,
            remSpaceOrStartPressed = false,
            remEscOrBackPressed = false;

        bool gamePadUp = false,
            gamePadDown = false,
            gamePad2Up = false,
            gamePad2Down = false;

        bool multiplayer = false;
        GameMode gameMode = GameMode.Menu;

        int currentMenuItem = 0;

        AudioEngine audioEngine;
        WaveBank waveBank;
        SoundBank soundBank;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
            base.Initialize();

            width = graphics.GraphicsDevice.Viewport.Width;
            height = graphics.GraphicsDevice.Viewport.Height;

            backgroundTexture = Content.Load<Texture2D>("SpaceBackground");
            menuTexture = Content.Load<Texture2D>("PongMenu");
            gameTexture = Content.Load<Texture2D>("PongGame");

            audioEngine = new AudioEngine("sound.xgs");
            waveBank = new WaveBank(audioEngine, "Wave Bank.xwb");
            if (waveBank != null)
                soundBank = new SoundBank(audioEngine, "Sound Bank.xsb");

            

        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            remUpPressed =
                gamepad.DPad.Up == ButtonState.Pressed ||
                gamepad.ThumbSticks.Left.Y > 0.5f ||
                keyboard.IsKeyDown(Keys.Up);
            remDownPressed =
                gamepad.DPad.Down == ButtonState.Pressed ||
                gamepad.ThumbSticks.Left.Y < -0.5f ||
                keyboard.IsKeyDown(Keys.Down);
            remSpaceOrStartPressed =
                gamepad.Buttons.Start == ButtonState.Pressed ||
                gamepad.Buttons.A == ButtonState.Pressed ||
                keyboard.IsKeyDown(Keys.LeftControl) ||
                keyboard.IsKeyDown(Keys.RightControl) ||
                keyboard.IsKeyDown(Keys.Space) ||
                keyboard.IsKeyDown(Keys.Enter);
            remEscOrBackPressed =
                gamepad.Buttons.Back == ButtonState.Pressed ||
                keyboard.IsKeyDown(Keys.Escape);

            // Get current gamepad and keyboard states
            gamepad = GamePad.GetState(PlayerIndex.One);
            gamepad2 = GamePad.GetState(PlayerIndex.One);
            keyboard = Keyboard.GetState();

            gamePad2Up = gamepad2.DPad.Up == ButtonState.Pressed || gamepad2.ThumbSticks.Left.Y > 0.5f;
            gamePad2Down = gamepad2.DPad.Down == ButtonState.Pressed || gamepad2.ThumbSticks.Right.Y < -0.5f;

            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || keyboard.IsKeyDown(Keys.Escape))
                this.Exit();

            float moveFactorPerSecond = 0.5f * (float)gameTime.ElapsedRealTime.TotalMilliseconds / 1000.0f;

            if (keyboard.IsKeyDown(Keys.Up))
                rightPaddlePosition -= moveFactorPerSecond;

            if (keyboard.IsKeyDown(Keys.Down))
                rightPaddlePosition += moveFactorPerSecond;


            if (multiplayer)
            {
                if (gamePad2Up || keyboard.IsKeyDown(Keys.W))
                    leftPaddlePosition -= moveFactorPerSecond;

                if (gamePad2Down || keyboard.IsKeyDown(Keys.S))
                    leftPaddlePosition += moveFactorPerSecond;
            }

            else
            {
                float computerChange = ComputerPaddleSpeed * moveFactorPerSecond;

                if (leftPaddlePosition > ballPosition.Y + computerChange)
                    leftPaddlePosition -= computerChange;

                else if (leftPaddlePosition < ballPosition.Y - computerChange)
                    leftPaddlePosition += computerChange;
            }

            if (leftPaddlePosition < 0)
                leftPaddlePosition = 0;

            if (leftPaddlePosition > 1)
                leftPaddlePosition = 1;

            if (rightPaddlePosition < 0)
                rightPaddlePosition = 0;

            if (rightPaddlePosition > 1)
                rightPaddlePosition = 1;

            ballPosition += ballSpeedVector * moveFactorPerSecond * BallSpeedMultiplicator;

            if (ballPosition.Y < 0 || ballPosition.Y > 0.75f)
            {
                ballSpeedVector.Y = -ballSpeedVector.Y;
                if (ballPosition.Y < 0)
                    ballPosition.Y = 0;
                if (ballPosition.Y > 0.75f)
                    ballPosition.Y = 0.75f;
            }
            // Check for collisions with the paddles
            // Construct bounding boxes to use the intersection helper method.
            Vector2 ballSize = new Vector2(
                GameBallRect.Width / 1024.0f, GameBallRect.Height / 768.0f);
            BoundingBox ballBox = new BoundingBox(
                new Vector3(ballPosition.X - ballSize.X / 2, ballPosition.Y - ballSize.Y / 2, 0),
                new Vector3(ballPosition.X + ballSize.X / 2, ballPosition.Y + ballSize.Y / 2, 0));
            Vector2 paddleSize = new Vector2(
                GameRedPaddleRect.Width / 1024.0f, GameRedPaddleRect.Height / 768.0f);
            BoundingBox leftPaddleBox = new BoundingBox(
                new Vector3(-paddleSize.X / 2, leftPaddlePosition - paddleSize.Y / 2, 0),
                new Vector3(+paddleSize.X / 2, leftPaddlePosition + paddleSize.Y / 2, 0));
            BoundingBox rightPaddleBox = new BoundingBox(
                new Vector3(1 - paddleSize.X / 2, rightPaddlePosition - paddleSize.Y / 2, 0),
                new Vector3(1 + paddleSize.X / 2, rightPaddlePosition + paddleSize.Y / 2, 0));

            // Ball hit left paddle?
            if (ballBox.Intersects(leftPaddleBox))
            {
                // Bounce of the paddle (always make positive)
                ballSpeedVector.X = Math.Abs(ballSpeedVector.X);
                // Increase speed a little
                ballSpeedVector *= 1.05f;
                // Did we hit the edges of the paddle?
                if (ballBox.Intersects(new BoundingBox(
                    new Vector3(leftPaddleBox.Min.X - 0.01f, leftPaddleBox.Min.Y - 0.01f, 0),
                    new Vector3(leftPaddleBox.Min.X + 0.01f, leftPaddleBox.Min.Y + 0.01f, 0))))
                    // Bounce of at a more difficult angle for the other player
                    ballSpeedVector.Y = -2;
                else if (ballBox.Intersects(new BoundingBox(
                    new Vector3(leftPaddleBox.Min.X - 0.01f, leftPaddleBox.Max.Y - 0.01f, 0),
                    new Vector3(leftPaddleBox.Min.X + 0.01f, leftPaddleBox.Max.Y + 0.01f, 0))))
                    // Bounce of at a more difficult angle for the other player
                    ballSpeedVector.Y = +2;
                // Play sound

                soundBank.PlayCue("PongBallHit");
            } // if

            // Ball hit right paddle?
            if (ballBox.Intersects(rightPaddleBox))
            {
                // Bounce of the paddle (always make negative)
                ballSpeedVector.X = -Math.Abs(ballSpeedVector.X);
                // Increase speed a little
                ballSpeedVector *= 1.05f;
                // Did we hit the edges of the paddle?
                if (ballBox.Intersects(new BoundingBox(
                    new Vector3(rightPaddleBox.Min.X - 0.01f, rightPaddleBox.Min.Y - 0.01f, 0),
                    new Vector3(rightPaddleBox.Min.X + 0.01f, rightPaddleBox.Min.Y + 0.01f, 0))))
                    // Bounce of at a more difficult angle for the other player
                    ballSpeedVector.Y = -2;
                else if (ballBox.Intersects(new BoundingBox(
                    new Vector3(rightPaddleBox.Min.X - 0.01f, rightPaddleBox.Max.Y - 0.01f, 0),
                    new Vector3(rightPaddleBox.Min.X + 0.01f, rightPaddleBox.Max.Y + 0.01f, 0))))
                    // Bounce of at a more difficult angle for the other player
                    ballSpeedVector.Y = +2;

                // Play sound
                soundBank.PlayCue("PongBallHit");
            } // if

            if (ballPosition.X < -0.065f)
            {
                soundBank.PlayCue("PongBallLost");
                leftPlayerLives--;
                StartNewBall();
            }

            else if (ballPosition.X > 1.065f)
            {
                soundBank.PlayCue("PongBallLost");
                rightPlayerLives--;
                StartNewBall();
            }

            if (gameMode == GameMode.Game && (leftPlayerLives == 0 || rightPlayerLives == 0))
            {
                gameMode = GameMode.GameOver;
            }

            // TODO: Add your update logic here

            base.Update(gameTime);
        }

        public void StartNewBall()
        {
            ballPosition = new Vector2(0.5f, 0.5f);
            Random rnd = new Random((int)DateTime.Now.Ticks);
            int direction = rnd.Next(4);

            ballSpeedVector =
                direction == 0 ? new Vector2(1, 0.8f) :
                direction == 1 ? new Vector2(1, -0.8f) :
                direction == 2 ? new Vector2(-1, 0.8f) :
                new Vector2(-1, -0.8f);

        }

        public void StopBall()
        {
            ballSpeedVector = new Vector2(0, 0);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // TODO: Add your drawing code here

            spriteBatch.Begin();
            spriteBatch.Draw(backgroundTexture, new Rectangle(0, 0, width, height), Color.LightGray);
            spriteBatch.End();

            // Show screen depending on our current screen mode
            if (gameMode == GameMode.Menu)
            {
                // Show menu
                RenderSprite(menuTexture,
                    512 - XnaPongLogoRect.Width / 2, 150, XnaPongLogoRect);
                RenderSprite(menuTexture,
                    512 - MenuSingleplayerRect.Width / 2, 300, MenuSingleplayerRect,
                    currentMenuItem == 0 ? Color.Orange : Color.White);
                RenderSprite(menuTexture,
                    512 - MenuMultiplayerRect.Width / 2, 350, MenuMultiplayerRect,
                    currentMenuItem == 1 ? Color.Orange : Color.White);
                RenderSprite(menuTexture,
                    512 - MenuExitRect.Width / 2, 400, MenuExitRect,
                    currentMenuItem == 2 ? Color.Orange : Color.White);

                // Note: Usually input should be handled in Update, but I really think
                // it is better to have to closely together with the UI code here!
                if ((keyboard.IsKeyDown(Keys.Down) ||
                    gamePadDown) &&
                    remDownPressed == false)
                {
                    currentMenuItem = (currentMenuItem + 1) % 3;
                    soundBank.PlayCue("PongBallHit");
                } // else if
                else if ((keyboard.IsKeyDown(Keys.Up) ||
                    gamePadUp) &&
                    remUpPressed == false)
                {
                    currentMenuItem = (currentMenuItem + 2) % 3;
                    soundBank.PlayCue("PongBallHit");
                } // else if
                else if ((keyboard.IsKeyDown(Keys.Space) ||
                    keyboard.IsKeyDown(Keys.LeftControl) ||
                    keyboard.IsKeyDown(Keys.RightControl) ||
                    keyboard.IsKeyDown(Keys.Enter) ||
                    gamepad.Buttons.A == ButtonState.Pressed ||
                    gamepad.Buttons.Start == ButtonState.Pressed ||
                    // Back or Escape exits our game on Xbox 360 and Windows
                    keyboard.IsKeyDown(Keys.Escape) ||
                    gamepad.Buttons.Back == ButtonState.Pressed) &&
                    remSpaceOrStartPressed == false &&
                    remEscOrBackPressed == false)
                {
                    // Quit app.
                    if (currentMenuItem == 2 ||
                        keyboard.IsKeyDown(Keys.Escape) ||
                        gamepad.Buttons.Back == ButtonState.Pressed)
                    {
                        this.Exit();
                    } // if
                    else
                    {
                        // Start game
                        gameMode = GameMode.Game;
                        leftPlayerLives = 3;
                        rightPlayerLives = 3;
                        leftPaddlePosition = 0.5f;
                        rightPaddlePosition = 0.5f;
                        StartNewBall();
                        // Set multiplayer if it was selected in menu.
                        // Otherwise the computer controls the left paddle
                        multiplayer = currentMenuItem == 1;
                    } // else
                } // else if
            } // if
            else
            {
                // Show lives
                ShowLives();

                // Ball in center
                RenderBall();
                // Render both paddles
                RenderPaddles();

                // If game is over, show winner
                if (gameMode == GameMode.GameOver)
                {
                    if (leftPlayerLives == 0)
                        RenderSprite(menuTexture,
                            512 - GameBlueWonRect.Width / 2, 300, GameBlueWonRect);
                    else
                        RenderSprite(menuTexture,
                            512 - GameRedWonRect.Width / 2, 300, GameRedWonRect);

                    // A, Space or Enter returns us to the main menu.
                    // Esc and Back do return the the menu too, see below.
                    // Make sure the keys are released again when we enter the main menu.
                    if ((gamepad.Buttons.A == ButtonState.Pressed ||
                        keyboard.IsKeyDown(Keys.Space) ||
                        keyboard.IsKeyDown(Keys.Enter)) &&
                        remSpaceOrStartPressed == false)
                        gameMode = GameMode.Menu;
                } // if

                // Back and Escape always quits to the main menu
                if ((gamepad.Buttons.Back == ButtonState.Pressed ||
                    keyboard.IsKeyDown(Keys.Escape)) &&
                    remEscOrBackPressed == false)
                {
                    gameMode = GameMode.Menu;
                    StopBall();
                } // if
            } // else

            DrawSprite();

            base.Draw(gameTime);

        }

        public void RenderSprite(Texture2D texture, int x, int y, Rectangle? source, Color color)
        {
            RenderSprite(texture,
                new Rectangle(x, y, source.Value.Width, source.Value.Height),
                source, color);
        }

        public void RenderSprite(Texture2D texture, int x, int y, Rectangle? source)
        {
            RenderSprite(texture,
                new Rectangle(x, y, source.Value.Width, source.Value.Height),
                source, Color.White);
        }

        public void RenderSprite(Texture2D texture, Rectangle rect, Rectangle? source, Color color)
        {
            sprites.Add(new SpriteToRender(texture, rect, source, color));
        }


        delegate void TestDelegate();
        class TestPongGame : Game1
        {
            TestDelegate testLoop;
            public TestPongGame(TestDelegate setTestLoop)
            {
                testLoop = setTestLoop;
            }

            protected override void Draw(GameTime gameTime)
            {
                base.Draw(gameTime);
                testLoop();
            }
        }

        static TestPongGame testGame;
        static void StartTest(TestDelegate testLoop)
        {
            using (testGame = new TestPongGame(testLoop))
            {
                testGame.Run();
            }
        }

        public static void TestMenuSprite()
        {
            StartTest(
                delegate
                {
                    testGame.RenderSprite(testGame.menuTexture, 512 - XnaPongLogoRect.Width / 2, 150, XnaPongLogoRect);
                    testGame.RenderSprite(testGame.menuTexture, 512 - MenuSingleplayerRect.Width / 2, 300, MenuSingleplayerRect);
                    testGame.RenderSprite(testGame.menuTexture, 512 - MenuMultiplayerRect.Width / 2, 350, MenuMultiplayerRect, Color.Orange);
                    testGame.RenderSprite(testGame.menuTexture, 512 - MenuExitRect.Width / 2, 400, MenuExitRect);
                });
        }

        public static void TestGameSprites()
        {
            StartTest(
                delegate
                {
                    testGame.ShowLives();
                    testGame.RenderBall();
                    testGame.RenderPaddles();
                });
        }

        public static void TestBallCollisions()
        {
            StartTest(
                delegate
                {
                    testGame.gameMode = GameMode.Game;
                    testGame.multiplayer = false;
                    testGame.Window.Title = "test pingpong";

                    if (testGame.keyboard.IsKeyDown(Keys.D1))
                    {
                        testGame.ballPosition = new Vector2(0.6f, 0.9f);
                        testGame.ballSpeedVector = new Vector2(1, 1);
                    }

                    else if (testGame.keyboard.IsKeyDown(Keys.D2))
                    {
                        testGame.ballPosition = new Vector2(0.9f, 0.6f);
                        testGame.ballSpeedVector = new Vector2(1, 1);
                        testGame.rightPaddlePosition = 0.7f;
                    }

                    else if (testGame.keyboard.IsKeyDown(Keys.D3))
                    {
                        testGame.ballPosition = new Vector2(0.1f, 0.4f);
                        testGame.ballSpeedVector = new Vector2(-1, -0.5f);
                        testGame.leftPaddlePosition = 0.35f;
                    }

                    else if (testGame.keyboard.IsKeyDown(Keys.D4))
                    {
                        testGame.ballPosition = new Vector2(0.9f, 0.4f);
                        testGame.ballSpeedVector = new Vector2(1, -0.5f);
                        testGame.rightPaddlePosition = 0.29f;
                    }

                    else if (testGame.keyboard.IsKeyDown(Keys.D5))
                    {
                        testGame.ballPosition = new Vector2(0.9f, 0.4f);
                        testGame.ballSpeedVector = new Vector2(1, -0.5f);
                        testGame.leftPaddlePosition = 0.42f;
                    }

                    testGame.ShowLives();

                    testGame.RenderPaddles();

                    testGame.RenderBall();
                });
        }

        public void ShowLives()
        {
            RenderSprite(menuTexture, 2, 2, GameLivesRect);
            for (int num = 0; num < leftPlayerLives; num++)
            {
                RenderSprite(gameTexture, 2 + GameLivesRect.Width + GameSmallBallRect.Width * num - 2, 9, GameSmallBallRect);
            }
            int rightX = 1024 - GameLivesRect.Width - GameSmallBallRect.Width * 3 - 4;
            RenderSprite(menuTexture, rightX, 2, GameLivesRect);

            for (int num = 0; num < rightPlayerLives; num++)
            {
                RenderSprite(gameTexture, rightX + GameLivesRect.Width + GameSmallBallRect.Width * num - 2, 9, GameSmallBallRect);
            }
        }

        public void RenderBall()
        {
            RenderSprite(gameTexture, (int)((0.05f + 0.9f * ballPosition.X) * 1024) - GameBallRect.Width / 2, (int)((0.02f + 0.96f * ballPosition.Y) * 768) - GameBallRect.Height / 2, GameBallRect);
        }

        public void RenderPaddles()
        {
            RenderSprite(gameTexture,
                (int)(0.05f * 1024) - GameRedPaddleRect.Width / 2, 
                (int)((0.06f + 0.88f * leftPaddlePosition) * 768) - GameRedPaddleRect.Height / 2, GameRedPaddleRect);
            RenderSprite(gameTexture,
                (int)(0.95f * 1024) - GameBluePaddleRect.Width / 2,
                (int)((0.06f + 0.88f * rightPaddlePosition) * 768) - GameBluePaddleRect.Height / 2, GameBluePaddleRect);

        }

        public void DrawSprite()
        {
            if (sprites.Count == 0)
            {
                return;
            }

            spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.BackToFront, SaveStateMode.None);

            foreach (SpriteToRender sprite in sprites)
            {
                spriteBatch.Draw(sprite.texture,
                    new Rectangle(sprite.rect.X * width / 1024, sprite.rect.Y * width / 768, sprite.rect.Width * height / 1024, sprite.rect.Height * height / 768),
                    sprite.source, sprite.color);
            }

            spriteBatch.End();

            sprites.Clear();
        }

        class SpriteToRender
        {
            public Texture2D texture;
            public Rectangle rect;
            public Rectangle? source;
            public Color color;
            public SpriteToRender(Texture2D setTexture, Rectangle setRect, Rectangle? setSource, Color setColor)
            {
                texture = setTexture;
                rect = setRect;
                source = setSource;
                color = setColor;
            }
        }

        List<SpriteToRender> sprites = new List<SpriteToRender>();
    }
}
